/*
 *
 *
 */
#include "gkt_board.h"
#include "gkt_debug.h"
#include "gkt_list.h"
#include "gkt_os.h"
#include "gkt_bg_job.h"

#ifdef GKT_CONFIG_BG_JOB_TABLE

typedef struct _bg_job_item {
	gkt_list_s	link;
	volatile uint32_t	flags;
	gkt_bg_job_handler	handler;
} bg_job_item_s;

typedef struct _bg_job_irq_item {
	uint32_t	id;
	uint32_t	flags;
} bg_job_irq_item_s;

#define BG_JOB_INSTANCE_F_INIT_OK	(1U << 0)
#define BG_JOB_INSTANCE_F_ENABLED	(1U << 1)
typedef struct _bg_job_instance {
	uint32_t	flags;

	gkt_mqueue_t	mqueue;
	gkt_mutex_t	mutex;
	gkt_semaphore_t	semaphore;

	gkt_list_s	pending_heads[GKT_BG_JOB_PRIORITY_MAX];
	bg_job_item_s	items[GKT_BG_JOB_ID_MAX];
	bg_job_item_s	*busy_item;
} bg_job_instance_s;
static bg_job_instance_s s_bg_job_instancce;

static int bg_job_start(uint32_t job_id, uint32_t flags)
{
	bg_job_item_s *item;
	uint32_t priority;

	item = &s_bg_job_instancce.items[job_id];
	if (!item->handler) {
		gkt_error("bg_job_start: job not register!\n");
		return GKT_ENOTCONFIG;
	}

	priority = flags & GKT_BG_JOB_F_PRIORITY_MASK;
	if (priority >= GKT_BG_JOB_PRIORITY_MAX)
		return GKT_EPARAM;

	gkt_mutex_lock(s_bg_job_instancce.mutex, GKT_INFINITE);
	if (s_bg_job_instancce.busy_item != item) {
		item->flags = flags;
		gkt_list_delete(&item->link);
		gkt_list_add_tail(&s_bg_job_instancce.pending_heads[priority], &item->link);
		gkt_mutex_unlock(s_bg_job_instancce.mutex);
	}
	else {
		gkt_mutex_unlock(s_bg_job_instancce.mutex);
		gkt_error("bg_job_pending_insert: bg_job(%u) is BUSY!\n", job_id);
		return GKT_EBUSY;
	}

	if (s_bg_job_instancce.flags & BG_JOB_INSTANCE_F_ENABLED)
		gkt_semaphore_release(s_bg_job_instancce.semaphore);

	return GKT_SUCCESS;
}

static void bg_job_irq_task_routine(void *params)
{
	bg_job_irq_item_s irq_item;
	int retval;

	do {
		irq_item.id = ~0;
		retval = gkt_mqueue_get(s_bg_job_instancce.mqueue,
					&irq_item, GKT_INFINITE);
		if ((GKT_SUCCESS == retval)
			&& (irq_item.id < GKT_BG_JOB_ID_MAX))
		{
			bg_job_start(irq_item.id, irq_item.flags);
		}
	} while (1);
}

static void bg_job_task_routine(void *params)
{
	gkt_list_s *item_pos;
	uint32_t priority;
	int retval;

	do {
		retval = gkt_semaphore_acquire(s_bg_job_instancce.semaphore, 
						GKT_INFINITE);
		if (retval != GKT_SUCCESS)
			continue;
	
		do {
			gkt_mutex_lock(s_bg_job_instancce.mutex, GKT_INFINITE);
			priority = GKT_BG_JOB_PRIORITY_HIGH;
			while (priority < GKT_BG_JOB_PRIORITY_MAX) {
				if (gkt_list_is_not_empty(s_bg_job_instancce.pending_heads[priority])) {
					item_pos = gkt_list_first(s_bg_job_instancce.pending_heads[priority]);
					gkt_list_delete(item_pos);
					s_bg_job_instancce.busy_item = __CONTAINER_OF(item_pos, bg_job_item_s, link);
					break;
				}
				priority++;
			}
			gkt_mutex_unlock(s_bg_job_instancce.mutex);

			if (__LIKELY(s_bg_job_instancce.busy_item)) {
#if defined(GKT_CONFIG_DEBUG_ENABLE) && GKT_CONFIG_DEBUG_ENABLE
				uint32_t handling_time;

				handling_time = gkt_systime_ms();
				(*s_bg_job_instancce.busy_item->handler)();
				handling_time = gkt_systime_ms() - handling_time;
				gkt_info("bg_job_task: <%u> time = %ums\n", 
						s_bg_job_instancce.busy_item - s_bg_job_instancce.items, 
						handling_time);
#else
				(*s_bg_job_instancce.busy_item->handler)();
#endif

				gkt_mutex_lock(s_bg_job_instancce.mutex, GKT_INFINITE);
				s_bg_job_instancce.busy_item = NULL;
				gkt_mutex_unlock(s_bg_job_instancce.mutex);
			}
			else
				break;
		}while (1);
	} while (1);
}

int gkt_bg_job_init(void)
{
	gkt_thread_t thread_id;
	uint32_t i;

	gkt_trace("bg_job_init: job nums = %u\n", GKT_BG_JOB_ID_MAX);

	s_bg_job_instancce.flags = 0;
	for (i = 0; i < GKT_BG_JOB_PRIORITY_MAX; i++)
		gkt_list_init(&s_bg_job_instancce.pending_heads[i]);
	for (i = 0; i < GKT_BG_JOB_ID_MAX; i++) {
		s_bg_job_instancce.items[i].flags = 0;
		gkt_list_init(&s_bg_job_instancce.items[i].link);
	}

	s_bg_job_instancce.mutex = gkt_mutex_create(0);
	if (!s_bg_job_instancce.mutex) {
		gkt_error("bg_job_init: create mutex failed!\n");
		return GKT_ECREATE;
	}

	s_bg_job_instancce.semaphore = gkt_semaphore_create(1, 0);
	if (!s_bg_job_instancce.semaphore) {
		gkt_error("bg_job_init: create semaphore failed!\n");
		return GKT_ECREATE;
	}

	s_bg_job_instancce.mqueue = gkt_mqueue_create(GKT_BG_JOB_ID_MAX, 
				sizeof(bg_job_irq_item_s));
	if (__UNLIKELY(!s_bg_job_instancce.mqueue)) {
		gkt_error("bg_job_init: create mqueue failed.\n");
		return GKT_ENOMEMORY;
	}

	thread_id = gkt_thread_create(bg_job_irq_task_routine, NULL,
					GKT_THREAD_PRIORITY_HIGH, 0x400, NULL);
	if (!thread_id) {
		gkt_error("bg_job_init: create irq task failed!\n");
		return GKT_ECREATE;
	}

	thread_id = gkt_thread_create(bg_job_task_routine, NULL,
					GKT_THREAD_PRIORITY_NORMAL, 0x1000, NULL);
	if (!thread_id) {
		gkt_error("bg_job_init: create task failed!\n");
		return GKT_ECREATE;
	}

	s_bg_job_instancce.flags |= BG_JOB_INSTANCE_F_INIT_OK;

	return GKT_SUCCESS;
}

void gkt_bg_job_register(uint32_t job_id, 
				gkt_bg_job_handler job_handler)
{
	gkt_trace("bg_job_register: id(%u), handler(%p)\n",
				job_id, job_handler);
	if ((job_id < GKT_BG_JOB_ID_MAX) && job_handler) {
		if (!(s_bg_job_instancce.flags & BG_JOB_INSTANCE_F_INIT_OK)) {
			gkt_error("bg_job_register: not init ok!\n");
			return;
		}
		else if (s_bg_job_instancce.flags & BG_JOB_INSTANCE_F_ENABLED) {
			gkt_error("bg_job_register: enabled!\n");
			return;
		}

		s_bg_job_instancce.items[job_id].handler = job_handler;
	}
}

void gkt_bg_job_enable(void)
{
	gkt_trace("bg_job_enable: ...\n");

	if (!(s_bg_job_instancce.flags & BG_JOB_INSTANCE_F_INIT_OK)) {
		gkt_error("bg_job_enable: not init ok!\n");
		return;
	}

	if (!(s_bg_job_instancce.flags & BG_JOB_INSTANCE_F_ENABLED)) {
		s_bg_job_instancce.flags |= BG_JOB_INSTANCE_F_ENABLED;
		gkt_semaphore_release(s_bg_job_instancce.semaphore);
	}
}

int gkt_bg_job_start(uint32_t job_id, uint32_t flags)
{
	bg_job_irq_item_s irq_item;
	int retval;

	gkt_info("bg_job_start: id(%u), flags(0x%x)\n", job_id, flags);

	if (job_id < GKT_BG_JOB_ID_MAX) {
		if (!(s_bg_job_instancce.flags & BG_JOB_INSTANCE_F_INIT_OK)) {
			gkt_error("bg_job_start: not init ok!\n");
			return GKT_ENOTINIT;
		}

		if (gkt_irq_context()) {
			irq_item.id = job_id;
			irq_item.flags = flags;
			retval = gkt_mqueue_put(s_bg_job_instancce.mqueue,
						&irq_item, GKT_IMMEDIATELY);
			if (retval != GKT_SUCCESS) {
				gkt_error("bg_job_start: (%u) mqueue put failed %d!\n", 
					job_id, retval);
			}
		}
		else
			retval = bg_job_start(job_id, flags);
	}
	else
		retval = GKT_EPARAM;

	return retval;
}

int gkt_bg_job_sync_complete(void)
{
	gkt_list_s *item_pos;
	bg_job_item_s *item;
	uint32_t priority;

	if (s_bg_job_instancce.flags & BG_JOB_INSTANCE_F_INIT_OK) {
		gkt_mutex_lock(s_bg_job_instancce.mutex, GKT_INFINITE);
		if (s_bg_job_instancce.busy_item 
			&& (s_bg_job_instancce.busy_item->flags & GKT_BG_JOB_F_SYNC))
		{
			gkt_mutex_unlock(s_bg_job_instancce.mutex);
			return 0;
		}

		priority = GKT_BG_JOB_PRIORITY_HIGH;
		while (priority < GKT_BG_JOB_PRIORITY_MAX) {
			gkt_list_foreach(&s_bg_job_instancce.pending_heads[priority], item_pos) {
				item = __CONTAINER_OF(item_pos, bg_job_item_s, link);
				if (item->flags & GKT_BG_JOB_F_SYNC) {
					gkt_mutex_unlock(s_bg_job_instancce.mutex);
					return 0;
				}
			}
			priority++;
		}

		gkt_mutex_unlock(s_bg_job_instancce.mutex);
	}

	return 1;
}

#endif

